;(function () {
  const windowWidth = window.innerWidth;
  const windowHeight = window.innerHeight;

  class Upload extends HTMLElement {
    constructor() {
      super();
      this.openWidth = this.getAttribute("openWidth") || 300; // 弹出层宽度
      this.openHeight = this.getAttribute("openHeight") || 450; // 弹出层高度

      this.blockWidth = this.getAttribute("blockWidth") || 300; // 块上传宽度
      this.blockHeight = this.getAttribute("blockHeight") || 50; // 块上传高度

      this.ePos = { x: 0, y: 0 }; // 鼠标位置
      this.rect = { x: 0, y: 0 }; // openEle位置

      this.shadow = this.attachShadow({ mode: "open" });

      this.type = this.getAttribute("type") || "button-open"; // 组件模式
      this.name = this.getAttribute("name") || undefined; // 组件名字

      if (!~["open", "button-open", "block"].indexOf(this.type)) {
        this.type = "button-open";
        console.error("警告：您传入的type模式不支持，已使用默认模式！");
      }

      this.listEles = {}; // 当前正在上传的列表元素
      this.customData = undefined;  // 传递的数据

      // 组件初始化事件，导出当前对象，用于自定义
      this.on("onInit", this);

      // 上传配置
      let rOpt = {};
      if (this.hasAttribute("rOpt")) {
        const opt = this.getAttribute("rOpt");
        if (~opt.indexOf("{")) {
          try {
            eval("rOpt=" + opt);
          } catch (e) {
            console.error(
              "警告：您传入的rOpt参数类型错误，解析失败，已使用默认值！"
            );
          }
        } else {
          if (window[opt] && typeof window[opt] === "object") {
            rOpt = window[opt];
          } else {
            console.error(
              "警告：您传入的rOpt参数类型错误，解析失败，已使用默认值！"
            );
          }
        }
      }

      this.rOpt = Object.assign(
        {},
        {
          // 接口地址（默认 `/` ）
          target: "/upload",
          // 切片字节大小（默认1*1024*1024）
          chunkSize: 1 * 1024 * 1024,
          // 同时上传切片个数(默认3)
          simultaneousUploads: 5,
          // 上传文件参数名(默认file)
          fileParameterName: "file",
          // 额外参数
          query: {},
          // 额外header头
          headers: {},
          // 先处理所有文件的第一块和最后一块。如果您可以仅从第一个或最后一个块中确定文件对您的服务有效，那么这将很方便。例如，照片或视频元数据通常位于文件的第一部分，从而可以轻松地仅从第一块测试支持。(默认false)
          prioritizeFirstAndLastChunk: false,
          // 向服务器发出每个块的GET请求，以查看是否已经存在。如果在服务器端实现，则即使在浏览器崩溃或计算机重启后，也可以恢复上传。(默认为true)
          testChunks: false,
          // 覆盖为每个文件生成唯一标识符的函数。（默认值：null）
          generateUniqueIdentifier: null,
          // 指示单个会话中可以上传多少文件。有效值为任何正整数，undefined没有限制。（默认值：undefined）
          maxFiles: undefined,
          // 一次显示请上传n个文件的功能。（默认值：显示一个警告框，并显示消息“请一次输入一个文件。”）
          // maxFilesErrorCallback: '',
          throttleProgressCallbacks: 1, // 不知道干什么的，不要删
        },
        rOpt
      );

      // 块上传时，强制个数为1个
      if (this.type === "block") {
        this.rOpt.maxFiles = 1;
      }

      // 如果未加载依赖文件，则先加载依赖文件
      if (!window.Resumable) {
        this.loadJs(
          "https://cdn.jsdelivr.net/npm/resumablejs@1.1.0/resumable.min.js"
        )
          .then((res) => {
            this.init();
          })
          .catch((err) => {
            throw "上传文件依赖加载失败：" + err;
          });
      } else {
        this.init();
      }
    }

    // 初始化组件
    init() {
      // 初始化上传对象
      this.r = new Resumable(this.rOpt);

      // 不支持Resumable.js
      if (!this.r.support) {
        alert("您用的浏览器也太low了吧，建议您换成Chrome浏览器再打开。");
        throw "浏览器不支持分片上传！";
      }

      // 添加监听
      this.listen();

      if (~this.type.indexOf("open")) {
        this.initOpen();
      } else if (this.type === "block") {
        this.initBlock();
      }
    }

    // 初始化打开模式样式
    initOpen() {
      /* 上传弹出层容器 开始 */
      this.openEle = document.createElement("div");
      this.openEle.setAttribute(
        "style",
        this.getStyle({
          width: "0px",
          height: "0px",
          position: "fixed",
          top: windowHeight / 2 + "px",
          left: windowWidth / 2 + "px",
          backgroundColor: "#fff",
          transition: "all 350ms",
          boxShadow: "0 0 3px #333",
          overflow: "hidden",
          zIndex: 9999999999999
        })
      );

      // 不变大小总容器
      const boxEle = document.createElement("div");
      boxEle.setAttribute(
        "style",
        this.getStyle({
          width: this.openWidth + "px",
          height: this.openHeight + "px",
        })
      );

      // 顶部拖拽区域
      this.topEle = document.createElement("div");
      this.topEle.innerText = "上传文件";
      this.topEle.setAttribute(
        "style",
        this.getStyle({
          height: "40px",
          width: "100%",
          backgroundColor: "#eaeaea",
          cursor: "all-scroll",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
        })
      );
      this.topEle.onmousedown = this.down.bind(this);

      // 关闭按钮
      let closeEle = document.createElement("span");
      closeEle.innerText = "×";
      closeEle.title = "最小化";
      closeEle.setAttribute(
        "style",
        this.getStyle({
          width: "20px",
          height: "20px",
          position: "absolute",
          top: "10px",
          right: "10px",
          textAlign: "center",
          lineHeight: "20px",
          fontSize: "30px",
          cursor: "pointer",
          zIndex: 2,
        })
      );
      closeEle.onclick = this.close.bind(this);

      // 总上传进度条
      const progressBoxEle = document.createElement("div");
      this.progressEle = document.createElement("div");
      progressBoxEle.setAttribute(
        "style",
        this.getStyle({
          width: "100%",
          height: "3px",
        })
      );
      this.progressEle.setAttribute(
        "style",
        this.getStyle({
          width: "0%",
          height: "2px",
          borderRadius: "3px",
          backgroundColor: "#3bff96",
        })
      );

      // 工具栏
      const toolsEle = document.createElement("div");
      const selectEle = document.createElement("div");
      this.controlEle = document.createElement("div");
      this.cancelEle = document.createElement("div");
      selectEle.innerText = "选择文件";
      this.controlEle.innerText = "开始上传";
      this.cancelEle.innerText = "取消上传";
      toolsEle.setAttribute(
        "style",
        this.getStyle({
          width: "100%",
          height: "40px",
          display: "flex",
          borderBottom: "1px solid #f3f3f3",
        })
      );
      selectEle.style = this.controlEle.style = this.cancelEle.style = this.getStyle(
        {
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          cursor: "pointer",
        }
      );
      selectEle.style.borderRight = "1px solid #f3f3f3";
      this.cancelEle.style.borderLeft = "1px solid #f3f3f3";
      this.controlEle.style.display = "none";
      this.cancelEle.style.display = "none";
      this.cancelEle.onclick = this.cancel.bind(this);
      this.controlEle.onclick = this.upload.bind(this);

      // 上传列表容器
      this.downBoxEle = document.createElement("div");
      this.downBoxEle.setAttribute(
        "style",
        this.getStyle({
          width: "100%",
          height: `calc(${this.openHeight}px - 40px - 3px - 40px - 50px)`,
          boxSizing: "border-box",
          padding: "10px",
          overflowY: "auto",
        })
      );

      // 确定按钮
      this.confirmEle = document.createElement("div");
      this.confirmEle.innerText = this.getAttribute("confirmText") || "完成";
      this.confirmEle.setAttribute(
        "style",
        this.getStyle({
          position: "absolute",
          bottom: "10px",
          left: "10px",
          width: "calc(100% - 20px)",
          height: "40px",
          textAlign: "center",
          lineHeight: "40px",
          color: "#fff",
          background: "#00b800",
          display: "none",
          cursor: "pointer",
        })
      );
      this.confirmEle.onclick = this.confirm.bind(this);

      // 添加到容器
      boxEle.appendChild(this.topEle);
      boxEle.appendChild(closeEle);
      progressBoxEle.appendChild(this.progressEle);
      boxEle.appendChild(progressBoxEle);
      toolsEle.appendChild(selectEle);
      toolsEle.appendChild(this.controlEle);
      toolsEle.appendChild(this.cancelEle);
      boxEle.appendChild(toolsEle);
      boxEle.appendChild(this.downBoxEle);
      boxEle.appendChild(this.confirmEle);
      this.openEle.appendChild(boxEle);
      this.r.assignDrop(boxEle); // 指定拖拽区域
      this.r.assignBrowse(selectEle); // 指定点击选择区域
      /* 上传弹出层容器 结束 */

      /* 打开按钮 开始 */
      if (this.type === "button-open") {
        let button = document.createElement("button");
        button.type = "button";
        button.innerText = this.getAttribute("buttonText") || "上传";
        let buttonStyle = {};
        if (this.hasAttribute("buttonStyle")) {
          const opt = this.getAttribute("buttonStyle");
          if (window[opt] && typeof window[opt] === "object") {
            buttonStyle = window[opt];
          } else {
            console.error(
              "警告：您传入的buttonStyle参数类型错误，解析失败，已使用默认值！"
            );
          }
        }
        const style = Object.assign(
          {},
          {
            display: "inline-block",
            height: "38px",
            lineHeight: "38px",
            padding: "0 18px",
            backgroundColor: "#009688",
            color: "#fff",
            whiteSpace: "nowrap",
            textAlign: "center",
            fontSize: "14px",
            border: "none",
            borderRadius: "2px",
            cursor: "pointer",
            outline: "none",
          },
          buttonStyle
        );
        button.setAttribute("style", this.getStyle(style));
        button.onclick = this.switch.bind(this);
        this.shadow.appendChild(button);
      }
      /* 打开按钮 结束 */

      this.shadow.appendChild(this.openEle);
    }

    // 初始化块模式样式
    initBlock() {
      // 容器
      this.blockEle = document.createElement("div");
      let blockStyle = {};
      if (this.hasAttribute("blockStyle")) {
        const opt = this.getAttribute("blockStyle");
        if (window[opt] && typeof window[opt] === "object") {
          blockStyle = window[opt];
        } else {
          console.error(
            "警告：您传入的blockStyle参数类型错误，解析失败，已使用默认值！"
          );
        }
      }
      const style = Object.assign(
        {},
        {
          width: this.blockWidth + "px",
          height: this.blockHeight + "px",
          boxShadow: "0 0 2px #ccc",
        },
        blockStyle
      );
      this.blockEle.setAttribute("style", this.getStyle(style));
      this.uploadEle = document.createElement("div");
      this.uploadEle.innerText = "拖拽或点击上传";
      this.uploadEle.setAttribute(
        "style",
        this.getStyle({
          width: "100%",
          height: "100%",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          cursor: "pointer",
        })
      );

      // 添加到容器
      this.r.assignDrop(this.uploadEle); // 指定拖拽区域
      this.r.assignBrowse(this.uploadEle); // 指定点击选择区域
      this.blockEle.appendChild(this.uploadEle);
      this.shadow.appendChild(this.blockEle);
    }

    listen() {
      // 处理文件添加事件
      this.r.on("fileAdded", (file) => {
        if (this.on("onFileAdded", {file, name: this.name, data: this.customData}) === false) {
          this.r.removeFile(file);
          return;
        }
        // 显示暂停，隐藏开始
        if (~this.type.indexOf("open")) {
          this.cancelEle.style.display = "flex";
          this.controlEle.style.display = "flex";
          this.confirmEle.style.display = "none";
          this.controlEle.innerText = "暂停上传";
          this.controlEle.style.color = "#c10000";
          this.controlEle.onclick = this.pause.bind(this);

          // 将文件添加到列表
          this.addList(file, "downBoxEle");
        } else {
          this.uploadEle.style.display = "none";
          this.addList(file, "blockEle");
        }
        // 添加完文件直接开始上传
        this.r.upload();
      });

      // 监听开始上传事件
      this.r.on("uploadStart", () => {
        this.on("onUploadStart", {name: this.name, data: this.customData});
        if (~this.type.indexOf("open")) {
          this.controlEle.innerText = "暂停上传";
          this.controlEle.style.color = "#c10000";
          this.confirmEle.style.display = "none";
          this.controlEle.onclick = this.pause.bind(this);
        }
      });

      // 监听暂停事件
      this.r.on("pause", () => {
        this.on("onPause", {name: this.name, data: this.customData});
        if (~this.type.indexOf("open")) {
          this.controlEle.innerText = "开始上传";
          this.controlEle.style.color = "#00c100";
          this.controlEle.onclick = this.upload.bind(this);
        }
      });

      // 监听完成事件
      this.r.on("complete", () => {
        this.on("onComplete", {name: this.name, data: this.customData});
        if (~this.type.indexOf("open")) {
          if (Object.keys(this.listEles).length) {
            this.topEle.innerText = "上传文件（完成）";
            this.controlEle.style.display = "none";
            this.cancelEle.innerText = "清除全部";
            this.confirmEle.style.display = "block";
          } else {
            this.topEle.innerText = "上传文件";
            this.controlEle.style.display = "none";
            this.cancelEle.style.display = "none";
            this.confirmEle.style.display = "none";
          }
        }
      });

      // 监听成功事件
      this.r.on("fileSuccess", (file, message) => {
        this.on("onSuccess", {file, message, name: this.name, data: this.customData});
        this.listEles[file.uniqueIdentifier].success = message.data || message;
        this.listEles[file.uniqueIdentifier].status.innerText = "成功";
        this.listEles[file.uniqueIdentifier].status.style.color = "#00c100";
      });

      // 监听失败事件
      this.r.on("fileError", (file, message) => {
        this.on("onError", {file, message, name: this.name, data: this.customData});
        this.listEles[file.uniqueIdentifier].status.innerText = "失败";
        this.listEles[file.uniqueIdentifier].status.style.color = "#c10000";
      });

      // 监听上传速度
      this.r.on("fileProgress", (file) => {
        const progress = Math.floor(file.progress() * 100) + "%";
        const allProgress = Math.floor(this.r.progress() * 100) + "%";
        if (this.listEles[file.uniqueIdentifier]) {
          this.listEles[file.uniqueIdentifier].status.innerText = progress;
          this.listEles[file.uniqueIdentifier].progress.style.width = progress;
        }
        if (~this.type.indexOf("open")) {
          this.progressEle.style.width = allProgress;
          this.topEle.innerText = "上传文件（" + allProgress + "）";
        }
      });

      // 监听取消上传
      this.r.on("cancel", () => {
        this.on("onCancel", {name: this.name, data: this.customData});
        if (~this.type.indexOf("open")) {
          for (let key in this.listEles) {
            this.downBoxEle.removeChild(this.listEles[key].box);
          }
          this.listEles = {};
          this.topEle.innerText = "上传文件";
          this.progressEle.style.width = "0px";
          this.cancelEle.style.display = "none";
          this.confirmEle.style.display = "none";
        }
      });
    }

    // 开始上传
    upload() {
      if (this.on("onClickUpload", {name: this.name, data: this.customData}) === false) return;
      this.r.upload();
    }

    // 暂停上传
    pause() {
      if (this.on("onClickPause", {name: this.name, data: this.customData}) === false) return;
      this.r.pause();
    }

    // 取消上传
    cancel() {
      if (this.on("onClickCancel", {name: this.name, data: this.customData}) === false) return;
      this.r.cancel();
    }

    // 取消单个上传
    remove(file) {
      if (this.on("onClickRemove", {name: this.name, data: this.customData}) === false) return;
      this.r.removeFile(file);
      if (~this.type.indexOf("open")) {
        this.downBoxEle.removeChild(this.listEles[file.uniqueIdentifier].box);
      } else if (this.type === "block") {
        this.uploadEle.style.display = "flex";
        this.blockEle.removeChild(this.listEles[file.uniqueIdentifier].box);
      }
      delete this.listEles[file.uniqueIdentifier];
      this.upload();
    }

    // 点击完成按钮时执行
    confirm() {
      if (~this.type.indexOf("open")) {
        let datas = [];
        for (let key in this.listEles) {
          const success = this.listEles[key].success;
          const file = this.listEles[key].file;
          if (success) {
            datas.push({
              data: success,
              file: file,
            });
          }
          this.downBoxEle.removeChild(this.listEles[key].box);
        }
        this.listEles = {};
        this.topEle.innerText = "上传文件";
        this.progressEle.style.width = "0px";
        this.cancelEle.style.display = "none";
        this.confirmEle.style.display = "none";
        this.r.cancel();
        this.on("onConfirm", {datas, name: this.name, data: this.customData});
        this.close();
      }
    }

    // 执行元素属性方法
    on(name, ...args) {
      const onName = this.getAttribute(name);
      if (onName && window[onName] && typeof window[onName] === "function") {
        return window[onName](...args);
      }
      return true;
    }

    // 添加上传列表
    addList(file, box) {
      const listEle = document.createElement("div");
      const style = {
        width: "100%",
        height: "100%",
        borderRadius: "4px",
        position: "relative",
        overflow: "hidden",
        display: "flex",
        alignItems: "center",
        justifyContent: "space-between",
      };
      if (box === "downBoxEle") {
        style.height = "60px";
        style.marginBottom = "10px";
        style.boxShadow = "0 0 2px #ccc";
      }
      listEle.setAttribute("style", this.getStyle(style));
      let closeEle = document.createElement("span");
      closeEle.innerText = "×";
      closeEle.setAttribute(
        "style",
        this.getStyle({
          display: "none",
          width: "20px",
          height: "20px",
          position: "absolute",
          top: "0px",
          right: "0px",
          textAlign: "center",
          lineHeight: "20px",
          fontSize: "30px",
          cursor: "pointer",
          zIndex: 2,
        })
      );
      closeEle.onclick = this.remove.bind(this, file);
      listEle.onmouseenter = () => {
        closeEle.style.display = "block";
      };
      listEle.onmouseleave = () => {
        closeEle.style.display = "none";
      };
      const progressEle = document.createElement("div");
      progressEle.setAttribute(
        "style",
        this.getStyle({
          position: "absolute",
          top: "0px",
          left: "0px",
          width: "0%",
          height: "100%",
          backgroundColor: "#dbffec",
        })
      );
      const nameEle = document.createElement("div");
      nameEle.innerText = file.fileName;
      nameEle.setAttribute(
        "style",
        this.getStyle({
          position: "relative",
          marginLeft: "10px",
          marginRight: "10px",
          textOverflow: "-o-ellipsis-lastline",
          display: "-webkit-box",
          overflow: "hidden",
          textOverflow: "ellipsis",
          lineClamp: 2,
          "-webkit-line-clamp": 2,
          "-webkit-box-orient": "vertical",
        })
      );

      const statusEle = document.createElement("div");
      statusEle.innerText = "列队中";
      statusEle.setAttribute(
        "style",
        this.getStyle({
          position: "relative",
          flexShrink: 0,
          marginRight: "10px",
        })
      );
      listEle.appendChild(progressEle);
      listEle.appendChild(nameEle);
      listEle.appendChild(statusEle);
      listEle.appendChild(closeEle);

      this[box].appendChild(listEle);

      this.listEles[file.uniqueIdentifier] = {
        box: listEle,
        progress: progressEle,
        name: nameEle,
        status: statusEle,
        file: file,
      };
    }

    // 打开openEle
    open(data) {
      if(!~data.constructor.toString().indexOf('MouseEvent')) {
        this.customData = data;
      }
      if (~this.type.indexOf("open")) {
        this.openEle.style.top = (windowHeight - this.openHeight) / 2 + "px";
        this.openEle.style.left = (windowWidth - this.openWidth) / 2 + "px";
        this.openEle.style.width = this.openWidth + "px";
        this.openEle.style.height = this.openHeight + "px";
      }
    }

    // 关闭openEle
    close() {
      if (~this.type.indexOf("open")) {
        this.openEle.style.top = windowHeight / 2 + "px";
        this.openEle.style.left = windowWidth / 2 + "px";
        this.openEle.style.width = "0px";
        this.openEle.style.height = "0px";
      }
    }

    // 切换打开状态
    switch(data) {
      if (~this.type.indexOf("open")) {
        if (this.openEle.style.width === "0px") {
          this.open(data);
        } else {
          this.close();
        }
      }
    }

    // 绑定选择元素
    bindBrowse(ele) {
      this.r.assignBrowse(ele);
    }

    // 绑定拖拽元素
    bindDrop(ele) {
      this.r.assignDrop(ele);
    }

    // 鼠标按下
    down(e) {
      if (~this.type.indexOf("open")) {
        this.ePos = { x: e.clientX, y: e.clientY };
        this.rect = this.openEle.getBoundingClientRect();
        this.openEle.style.transitionDuration = "0ms";
        window.onmousemove = this.move.bind(this);
        window.onmouseup = this.up.bind(this);
      }
    }

    // 鼠标移动
    move(e) {
      if (~this.type.indexOf("open")) {
        const _x = e.clientX - this.ePos.x;
        const _y = e.clientY - this.ePos.y;
        this.openEle.style.left = this.rect.left + _x + "px";
        this.openEle.style.top = this.rect.top + _y + "px";
      }
    }

    // 鼠标抬起清理拖动
    up() {
      if (~this.type.indexOf("open")) {
        this.openEle.style.transitionDuration = "350ms";
        window.onmousemove = null;
        window.onmouseup = null;
      }
    }

    // 将对象样式整理成字符串样式
    getStyle(style) {
      let styleStr = "";
      for (let key in style) {
        const keyName = key.replace(
          /([A-Z])/g,
          (a) => `-${a.toLocaleLowerCase()}`
        );
        styleStr += `${keyName}:${style[key]};`;
      }
      return styleStr;
    }

    // 加载js
    loadJs(url) {
      return new Promise((resolve, reject) => {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.src = url;
        script.onload = resolve;
        script.onerror = reject;
        document.body.appendChild(script);
      });
    }
  }

  customElements.define("slice-upload", Upload);
})();
